package com.qlogic.commons.platform.impls.lucene.indexer;

import java.io.IOException;
import java.util.Hashtable;
import java.util.LinkedHashMap;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.FuzzyQuery;
import org.apache.lucene.search.Hits;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.PhraseQuery;
import org.apache.lucene.search.PrefixQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.RangeQuery;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.WildcardQuery;
import org.apache.lucene.store.Directory;

import com.qlogic.commons.exceptions.BaseException;
import com.qlogic.commons.platform.AbstractService;
import com.qlogic.commons.platform.ServiceActivationException;
import com.qlogic.commons.platform.ServiceDesactivationException;
import com.qlogic.commons.platform.ServiceExecutionException;
import com.qlogic.commons.platform.ServiceInitializationException;
import com.qlogic.commons.platform.ServiceStartingException;
import com.qlogic.commons.platform.ServiceStopingException;
import com.qlogic.commons.platform.impls.lucene.model.DocumentWrapper;
import com.qlogic.commons.platform.impls.lucene.model.FieldWrapper;

public class LuceneIndexerService extends AbstractService {
	
	static final String INDEXERS_PATH = "../../indexers/";
	static final String REGISTRY_PATH = "../../registry";
	static final String ADDED_CONTENT_QUEUE = "/addedContentQueue";
	static final String DELETED_CONTENT_QUEUE = "/deletedContentQueue";
	
	public static String CONTENT_ID = "contentId";
	
	public static final int MUST = 0;
	public static final int SHOULD = 1;
	public static final int MUST_NOT = 2;
	
	public static final String ARABIC = "arabic";
	public static final String FRENCH = "french";
	public static final String ENGLISH = "english";
	
	public static final String TEXT = "text";
	public static final String RTF = "rtf";
	public static final String HTML = "html";
	public static final String PDF = "pdf";
	public static final String DOC = "doc";
	public static final String PPT = "ppt";
	public static final String XLS = "xls";
	
	protected LinkedHashMap contents = new LinkedHashMap ();
	protected Hashtable searchHits = new Hashtable ();
	protected Hashtable queries = new Hashtable ();
	
	/**
	 * 
	 */
	private static final long serialVersionUID = -8270592547615189946L;

	/**
	 * @see com.qlogic.commons.platform.Service#initialize()
	 */
	public void initialize() throws ServiceInitializationException {
	}

	/**
	 * @see com.qlogic.commons.platform.Service#activate()
	 */
	public void activate() throws ServiceActivationException {
		activated = true;
	}

	/**
	 * @see com.qlogic.commons.platform.Service#start()
	 */
	public void start() throws ServiceStartingException {
		started = true;
	}

	/**
	 * @see com.qlogic.commons.platform.Service#desactivate()
	 */
	public void desactivate() throws ServiceDesactivationException {
		activated = false;
	}

	/**
	 * @see com.qlogic.commons.platform.Service#stop()
	 */
	public void stop() throws ServiceStopingException {
		started = false;
	}
	
	public String newContent (String id) throws ServiceExecutionException {
		if (id == null || id.trim ().equals ("")) {
			id = "content_" + contents.size ();
		}
		DocumentWrapper content = new DocumentWrapper (id);
		contents.put (id, content);
		addField (id, CONTENT_ID, id.getBytes (), null, true, true);
		return id;
	}

	public void addField (String contentId, String fieldName, byte [] value, String handler, boolean store, boolean tokenized, String encoding) throws ServiceExecutionException {
		DocumentWrapper documentWrapper = getContent (contentId);
		if (value == null || value.length <= 0) {
			throw new ServiceExecutionException ("Can not add null value for field [" + contentId + " --> " + fieldName + "]");
		}
		FieldWrapper fieldWrapper = 
			new FieldWrapper (fieldName, value, handler, store, tokenized, encoding); 
		documentWrapper.addField (fieldWrapper);
	}

	public void addField (String contentId, String fieldName, byte [] value, String handler, boolean store, boolean tokenized) throws ServiceExecutionException {
		addField (contentId, fieldName,  value, handler, store, tokenized, null);
	}
	public synchronized void add (String contentId, String indexer, boolean runNow) throws ServiceExecutionException {
		try {
			getContentRegistry ().registerForAdd (getContent (contentId), indexer);
		} catch (BaseException e) {
			throw new ServiceExecutionException (e);
		} 
		//new AddContentThread (contentId, getContent (contentId), configuration, indexer, runNow);
	}
	
	public synchronized void add (String contentId, String indexer) throws ServiceExecutionException {
		add (contentId, indexer, false);
	}
	
	public synchronized void addAll (String indexer, boolean runNow) throws ServiceExecutionException {
		try {
			getContentRegistry ().registerForAdd (contents, indexer);
		} catch (BaseException e) {
			throw new ServiceExecutionException (e);
		} 
		//new AddContentThread (contents, configuration, indexer, runNow);
	}
	
	public synchronized void addAll (String indexer) throws ServiceExecutionException {
		addAll (indexer, false);
	}
	
	public synchronized void delete (String contentId, String indexer, boolean runNow) throws ServiceExecutionException {
		try {
			getContentRegistry ().registerForDelete (contentId, indexer);
		} catch (BaseException e) {
			throw new ServiceExecutionException (e);
		} 
		//new DeleteContentThread (contentId, configuration, indexer, runNow);
	}
	
	public synchronized void delete (String contentId, String indexer) throws ServiceExecutionException {
		delete (contentId, indexer, false);
	}
	
	public synchronized void deleteAll (String fieldName, String fieldValue, String indexer, boolean runNow) throws ServiceExecutionException {
		try {
			getContentRegistry ().registerForDelete (fieldName, fieldValue, indexer);
		} catch (BaseException e) {
			throw new ServiceExecutionException (e);
		} 
		//new DeleteAllContentThread (fieldName, fieldValue, configuration, indexer, runNow);
	}
	
	public synchronized void deleteAll (String fieldName, String fieldValue, String indexer) throws ServiceExecutionException {
		deleteAll (fieldName, fieldValue, indexer, false);
	}
	
	public synchronized void update (String contentId, String indexer, boolean runNow) throws ServiceExecutionException {
		try {
			getContentRegistry ().registerForUpdate (getContent (contentId), indexer);
		} catch (BaseException e) {
			throw new ServiceExecutionException (e);
		} 
		//new UpdateContentThread (getContent (contentId), configuration, indexer, runNow);
	}
	
	public synchronized void update (String contentId, String indexer) throws ServiceExecutionException {
		update (contentId, indexer, false);
	}
	
	private Integer executeQuery (String indexer, Query query, String [] sortFields) throws ServiceExecutionException {
		try {
			Directory directory = getIndexDirectory (indexer);
			IndexSearcher searcher = new IndexSearcher (directory);
			Hits hits = null;
			if (sortFields != null && sortFields.length > 0) {
				SortField [] lSortFields = new SortField [sortFields.length];
				for (int i = 0; i < sortFields.length; i++) {
					lSortFields [i] = new SortField (sortFields [i], SortField.STRING);
				}
				Sort sort = new Sort (lSortFields);
				hits = searcher.search(query, sort);
			} else {
				hits = searcher.search(query);
			}
			
			if (hits == null || hits.length () <= 0) {
				return null;
			}
			Integer hitsId = new Integer (searchHits.size ());
			searchHits.put (hitsId, hits);
			return hitsId;
		} catch (BaseException e) {
			throw new ServiceExecutionException (e);
		} catch (IOException ioex) {
			throw new ServiceExecutionException (ioex);
		} 		
	}
	
	public String findById (String contentId, String indexer, boolean keep, String encoding) throws ServiceExecutionException {
		try {
			Directory directory = getIndexDirectory (indexer);
			IndexSearcher searcher = new IndexSearcher (directory);
			
			Term term = new Term (CONTENT_ID, contentId);
			Query query = new TermQuery (term);
			Hits hits = searcher.search(query);
			if (hits == null || hits.length () <= 0) {
				return null;
			}
			if (hits.length () > 1) {
				System.err.println ("More than one document found [" + hits.length () + "]");
			}
			Document content = hits.doc (0);
			if (keep) {
				contents.put (content.getField (CONTENT_ID).stringValue (), LuceneUtils.toDocumentWrapper (content, encoding));
			}
			return content.getField (CONTENT_ID).stringValue ();
		} catch (BaseException e) {
			throw new ServiceExecutionException (e);
		} catch (IOException ioex) {
			throw new ServiceExecutionException (ioex);
		} 
	}
	
	public Integer createTermQuery (String fieldName, String value) throws ServiceExecutionException {
		Term term = new Term (fieldName, value);
		Query query = new TermQuery (term);
		Integer queryId = new Integer (queries.size ());
		queries.put (queryId, query);
		return queryId;
	}
	
	public Integer termSearch (String fieldName, String value, String indexer, String [] sortFields) throws ServiceExecutionException {
		Term term = new Term (fieldName, value);
		Query query = new TermQuery (term);
		return executeQuery (indexer, query, sortFields);
	}
	
	public Integer termSearch (String fieldName, String value, String indexer) throws ServiceExecutionException {
		return termSearch (fieldName, value, indexer, null);
	}
	public Integer createExpressionQuery (String fieldName, String expression, String indexer) throws ServiceExecutionException {
		Analyzer analyzer;
		try {
			analyzer = (Analyzer)configuration.getResource (INDEXERS_PATH + indexer + "/analyzer").getValue ();
		} catch (BaseException e) {
			throw new ServiceExecutionException (e);
		} 
		QueryParser queryParser = new QueryParser (fieldName, analyzer);
		Query query;
		try {
			query = queryParser.parse (expression);
		} catch (ParseException e) {
			throw new ServiceExecutionException (e);
		}
		Integer queryId = new Integer (queries.size ());
		queries.put (queryId, query);
		return queryId;
	}
	
	public Integer expressionSearch (String fieldName, String expression, String indexer, String [] sortFields) throws ServiceExecutionException {
		Analyzer analyzer = null;
		try {
			analyzer = 
				(Analyzer)configuration.getResource (INDEXERS_PATH + indexer + "/analyzer").getValue ();
		} catch (BaseException e) {
			throw new ServiceExecutionException (e);
		} 
			
		QueryParser queryParser = new QueryParser (fieldName, analyzer);
		Query query;
		try {
			query = queryParser.parse (expression);
		} catch (ParseException e) {
			throw new ServiceExecutionException (e);
		}
		return executeQuery (indexer, query, sortFields);
	}
	
	public Integer expressionSearch (String fieldName, String expression, String indexer) throws ServiceExecutionException {
		return expressionSearch (fieldName, expression, indexer, null);
	}
	
	public Integer createRangQuery (String fieldName, String begin, String end, boolean inclusive) throws ServiceExecutionException {
		RangeQuery query = new RangeQuery (new Term (fieldName, begin), new Term (fieldName, end), inclusive);
		Integer queryId = new Integer (queries.size ());
		queries.put (queryId, query);
		return queryId;
	}
	
	public Integer rangSearch (String fieldName, String begin, String end, boolean inclusive, String indexer, String [] sortFields) throws ServiceExecutionException {
		RangeQuery query = new RangeQuery (new Term (fieldName, begin), new Term (fieldName, end), inclusive);
		return executeQuery (indexer, query, sortFields);
	}
	
	public Integer rangSearch (String fieldName, String begin, String end, boolean inclusive, String indexer) throws ServiceExecutionException {
		return rangSearch ( fieldName, begin, end, inclusive, indexer,null);
	}
	
	public Integer createStartsWithQuery (String fieldName, String value) throws ServiceExecutionException {
		Term term = new Term (fieldName, value);
		PrefixQuery query = new PrefixQuery (term);
		Integer queryId = new Integer (queries.size ());
		queries.put (queryId, query);
		return queryId;
	}
	
	public Integer startsWithSearch (String fieldName, String value, String indexer, String [] sortFields) throws ServiceExecutionException {
		Term term = new Term (fieldName, value);
		PrefixQuery query = new PrefixQuery (term);
		return executeQuery (indexer, query, sortFields);
	}
	
	public Integer startsWithSearch (String fieldName, String value, String indexer) throws ServiceExecutionException {
		return startsWithSearch (fieldName, value, indexer, null);
	}
	
	public Integer createPhraseQuery (String fieldName, String [] values, int slop) throws ServiceExecutionException {
		PhraseQuery query = new PhraseQuery ();
		query.setSlop (slop);
		for (int i = 0; i < values.length; i++) {
			query.add (new Term (fieldName, values [i]));
		}
		Integer queryId = new Integer (queries.size ());
		queries.put (queryId, query);
		return queryId;
	}
	
	public Integer phraseSearch (String fieldName, String [] values, int slop, String indexer, String [] sortFields) throws ServiceExecutionException {
		PhraseQuery query = new PhraseQuery ();
		query.setSlop (slop);
		for (int i = 0; i < values.length; i++) {
			query.add (new Term (fieldName, values [i]));
		}
		return executeQuery (indexer, query, sortFields);
	}
	
	public Integer phraseSearch (String fieldName, String [] values, int slop, String indexer) throws ServiceExecutionException {
		return phraseSearch (fieldName, values, slop, indexer, null);
	}
	
	public Integer createWildcardQuery (String fieldName, String expression) throws ServiceExecutionException {
		Query query = new WildcardQuery (new Term (fieldName, expression));
		Integer queryId = new Integer (queries.size ());
		queries.put (queryId, query);
		return queryId;
	}
	
	public Integer wildcardSearch (String fieldName, String expression, String indexer, String [] sortFields) throws ServiceExecutionException {
		Query query = new WildcardQuery (new Term (fieldName, expression));
		return executeQuery (indexer, query, sortFields);
	}
	
	public Integer wildcardSearch (String fieldName, String expression, String indexer) throws ServiceExecutionException {
		return wildcardSearch (fieldName, expression, indexer, null);
	}
	
	public Integer createFuzzyQuery (String fieldName, String value) throws ServiceExecutionException {
		Query query = new FuzzyQuery (new Term (fieldName, value));
		Integer queryId = new Integer (queries.size ());
		queries.put (queryId, query);
		return queryId;
	}
	
	public Integer fuzzySearch (String fieldName, String value, String indexer, String [] sortFields) throws ServiceExecutionException {
		Query query = new FuzzyQuery (new Term (fieldName, value));
		return executeQuery (indexer, query, sortFields);
	}
	
	public Integer fuzzySearch (String fieldName, String value, String indexer) throws ServiceExecutionException {
		return fuzzySearch (fieldName, value, indexer, null);
	}
	
	public Integer createBooleanQuery () throws ServiceExecutionException {
		Query query = new BooleanQuery ();
		Integer queryId = new Integer (queries.size ());
		queries.put (queryId, query);
		return queryId;
	}
	
	public void addToBooleanQuery (Integer booleanQuery, Integer query, int occur) throws ServiceExecutionException {
		Query storedQuery = (Query)queries.get (booleanQuery);
		if (storedQuery == null) {
			throw new ServiceExecutionException ("Query [" + booleanQuery + "] not found");
		}
		if (!(storedQuery instanceof BooleanQuery)) {
			throw new ServiceExecutionException ("Query [" + booleanQuery + "]. Only boolean query are permetted");
		}
		BooleanQuery boolQuery = (BooleanQuery)storedQuery;
		
		Query queryToAdd = (Query)queries.get (query);
		if (queryToAdd == null) {
			throw new ServiceExecutionException ("Query [" + query + "] not found");
		}

		if (occur == MUST) {
			boolQuery.add (queryToAdd, BooleanClause.Occur.MUST);
		} else if (occur == SHOULD) {
			boolQuery.add (queryToAdd, BooleanClause.Occur.SHOULD);
		} else {
			boolQuery.add (queryToAdd, BooleanClause.Occur.MUST_NOT);
		}
	}
	
	public Integer executeQuery (Integer query, String indexer, String [] sortFields) throws ServiceExecutionException {
		Query storedQuery = (Query)queries.get (query);
		if (storedQuery == null) {
			throw new ServiceExecutionException ("Query [" + query + "] not found");
		}
		return executeQuery (indexer, storedQuery, sortFields);
	}
	
	public Integer executeQuery (Integer query, String indexer) throws ServiceExecutionException {
		return executeQuery (query, indexer, null);
	}
	
	public int count (Integer hitsId) throws ServiceExecutionException {
		Hits hits = (Hits)get (hitsId);
		return hits.length ();
	}
	
	public float score (Integer hitsId, int index) throws ServiceExecutionException {
		Hits hits = (Hits)get (hitsId);
		try {
			return hits.score (index);
		} catch (IOException e) {
			throw new ServiceExecutionException (e);
		}
	}
	
	public String get (Integer hitsId, int index, String fieldName) throws ServiceExecutionException {
		Document document = (Document)get (hitsId, index);
		return document.getField (fieldName).stringValue ();
	}
	
	public Object get (Integer hitsId) throws ServiceExecutionException {
		Object hits = null;
		if (searchHits != null && !searchHits.isEmpty ()) {
			hits = searchHits.get (hitsId);
		}
		if (hits == null) {
			throw new ServiceExecutionException ("Query Results [" + hitsId + "] not found");
		}
		return hits;
	}
	
	public Object get (Integer hitsId, int index) throws ServiceExecutionException {
		Hits hits = (Hits)get (hitsId);
		Document document;
		try {
			document = hits.doc (index);
		} catch (IOException e) {
			throw new ServiceExecutionException (e);
		}
		return document;
	}
	
	public String get (Object doc, String fieldName) throws ServiceExecutionException {
		Document document = (Document)doc;
		Field field = document.getField (fieldName);
		if (field == null) {
			return null;
		}
		return field.stringValue ();
	}
	
	public String getContentId (Integer hitsId, int index) throws ServiceExecutionException {
		return get (hitsId, index, CONTENT_ID);
	}
	
	public String registerContent (Integer hitsId, int index) throws ServiceExecutionException {
		Document document = (Document)get (hitsId, index);
		String contentId = document.getField (CONTENT_ID).stringValue ();
		contents.put (contentId, document);
		return contentId;
	}
	
	private DocumentWrapper getContent (String id) throws ServiceExecutionException {
		if (id == null || id.trim().equals ("") || !contents.containsKey (id)) {
			throw new ServiceExecutionException ("Content [" + id + "] not found");
		}
		return (DocumentWrapper)contents.get (id);
	}
	
	public Directory getIndexDirectory (String indexer) throws ServiceExecutionException {
		try {
			return (Directory)configuration.getResource (INDEXERS_PATH + indexer + "/directory").getValue ();
		} catch (BaseException e) {
			throw new ServiceExecutionException (e);
		} 
	}
	
	public ContentRegistry getContentRegistry () throws ServiceExecutionException {
		try {
			return (ContentRegistry)configuration.getResource (REGISTRY_PATH).getValue ();
		} catch (BaseException e) {
			throw new ServiceExecutionException (e);
		} 
	}
	
}
